package net.imagine.core.resize;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import net.imagine.core.ImageInfo;
import net.imagine.core.ImageProcessResult;
import net.imagine.core.ImageProcessTask;
import net.imagine.core.ImageProcessTemplate;
import net.imagine.core.ResultState;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.util.StringUtils;

/**
 * <pre>
 * 提供一种图片压缩处理方式:
 * 1、把图片文件的输入流写到本地文件系统中（如果需要也可以在此步存储至分布式文件系统）
 * 2、把文件系统中的图片压缩成指定长/宽的缩略图
 * 3、把缩略图存储至分布式文件系统（可选）
 * 4、删除本地文件系统中的图片（可选）
 * 用户可以通过实现该类来实现个性化的命名和存储需要
 * </pre>
 * 
 * @see ImageProcessTemplate
 * @author wuqh
 */
public abstract class ImageResizeTemplate extends ImageProcessTemplate {
	private static final Log logger = LogFactory.getLog(ImageResizeTemplate.class);

	/**
	 * 压缩一张图片
	 * 
	 * @param imagePath
	 *            本地存放图片的目录。如果图片不上传到分布式文件系统的话，最终图片也将存放于此
	 * @param processTask
	 *            压缩任务
	 * 
	 * @return 图片处理结果
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T extends ImageProcessResult> T processImage(String imagePath, ImageProcessTask processTask)
			throws IOException {
		// 如果processTask不是压缩任务则不处理
		ImageResizeTask task = null;
		if (processTask instanceof ImageResizeTask) {
			task = (ImageResizeTask) processTask;
		}
		if (task == null) {
			logger.warn("task is not a image resize task");
			T result = (T) new ImageProcessResult(ResultState.UNKOWN_FAILED);
			result.setOriginalFilename(processTask.getFilename());
			return result;
		}

		// 对于本地文件，第一步，产生原片存放的key；第二步，存放源片；第二步，循环压缩并上传；第二步，处理压缩结果；第三步，后续处理或者删除源片
		// 对于非本地文件，第一步，产生源片存放的key；第二步，写成本地文件；其后同本地文件第二步
		List<String> fileLocations = new ArrayList<String>();// 用于存储所有使用过的文件名
		int[] baseSizes = task.getBaseSizes();
		int resizeCount = baseSizes.length;
		ResultState state = ResultState.UNKOWN_FAILED;
		String[] fileKeys = new String[resizeCount + 1];
		ImageInfo imageInfo = new ImageInfo();

		// 第一步，产生源片的存放的key
		String originalFilename = task.getFilename();
		// 通常key不带格式后缀
		String originalFileKey = generateFileKey(task);
		imageInfo.setOriginalFileKey(originalFileKey);

		// 第二步，如果不是本地文件，需要先写入到本地临时目录
		boolean isLocalFile = (task.getLocalStorePath() != null);
		File firstFile = null;
		if (!isLocalFile) {
			// 产生第一个文件的文件名，并写入到本地临时目录
			String firstFilename = generateFirstFilename(originalFileKey, originalFilename);
			String firstFilePath = generateFilePath(imagePath, firstFilename, task);
			firstFile = generateFile(firstFilePath);
			InputStream input = task.getInputStream();
			state = writeImageToFile(firstFile, input);
			if (state != ResultState.SUCCESS) {
				logger.warn("write inputstream to file failed");
				return (T) processResult(ResultState.NOT_IMAGE, task, fileKeys, imageInfo);
			}
			imageInfo.setLocalPath(firstFile.getPath());
			fileLocations.add(firstFile.getPath());
		} else {
			firstFile = new File(task.getLocalStorePath());
			fileLocations.add(task.getLocalStorePath());
		}

		ImageInfo tempInfo = operations.showImageInfo(fileLocations.get(0));
		if (tempInfo == null) {
			logger.warn("file is not an image");
			return (T) processResult(ResultState.NOT_IMAGE, task, fileKeys, imageInfo);
		} else {
			combineImageInfo(imageInfo, tempInfo);
		}

		// 第三步，存放源片
		if (firstFile != null) {
			state = storeFirstFile(firstFile, originalFileKey, fileKeys, task);
			if (state != ResultState.SUCCESS) {
				logger.warn("store original file failed");
				return (T) processResult(ResultState.NOT_IMAGE, task, fileKeys, imageInfo);
			}
		}

		String actualExt = imageInfo.getExt();
		// 第四步、压缩文件，并放到指定文件系统，如果失败则不需要继续压缩
		for (int i = 0; i < resizeCount; i++) {
			String currentFilename = generateFilename(originalFileKey, i, task, actualExt);
			String currentFileKey = generateResizeFileKey(originalFileKey, currentFilename, task);
			if (currentFilename == null) {// 总张数不符合期望
				state = ResultState.UNSUPPOR_BASESIZE_SIZE;
				break;
			}
			String curFilePath = generateFilePath(imagePath, currentFilename, task);
			File curLocalFile = generateFile(curFilePath);
			String preFilePath = fileLocations.get(i);
			File preLocalFile = generateFile(preFilePath);
			if (curLocalFile == null || preLocalFile == null) {
				logger.warn("create resize file failed when resize file:" + firstFile);
				state = ResultState.UNKOWN_FAILED;
				break;
			}
			fileLocations.add(curLocalFile.getPath());

			String preLocalFilePath = preLocalFile.getPath();
			String curLocalFilePath = curLocalFile.getPath();
			if (task.isSupportMulitFrame()) {
				state = operations.resizeImage(preLocalFilePath, curLocalFilePath, baseSizes[i]);
			} else {
				state = operations.resizeFirstFrame(preLocalFilePath, curLocalFilePath, baseSizes[i]);
			}

			if (state == ResultState.SUCCESS) {
				state = storeFile(curLocalFile, currentFileKey, fileKeys, i, task);
				if (state != ResultState.SUCCESS) {
					logger.warn("store resizeed image failed in index " + i);
					break;
				}
			} else {
				logger.warn("resize image " + task.getFilename() + " failed in index " + i);
				break;
			}
		}
		// 第五步、处理图片处理的结果
		T result = processResult(state, task, fileKeys, imageInfo);
		// 第四步、删除文件或者执行图片的下一环节操作
		if (this.nextProcessor != null) {
			if (isLocalFile) {
				return nextProcessor.processImage(imagePath, processTask);
			} else {// 非本地需要设置为本地
				processTask.setLocalStorePath(firstFile.getPath());
				return nextProcessor.processImage(imagePath, processTask);
			}
		} else {
			deleteFiles(fileLocations);
		}
		return result;
	}

	private void combineImageInfo(ImageInfo imageInfo, ImageInfo info) {
		if (imageInfo == null || info == null) {
			return;
		}
		String newExt = info.getExt();
		if (StringUtils.hasLength(newExt)) {
			imageInfo.setExt(newExt);
		}
		imageInfo.setHeight(info.getHeight());
		imageInfo.setWidth(info.getWidth());

		Map<String, String> meta = info.getMetaInfo();
		if (meta != null) {
			imageInfo.appendMetaInfo(meta);
		}
	}

	/**
	 * 产生压缩图片的key
	 * 
	 * @param originalFileKey
	 *            源片的key
	 * @param currentFilename
	 *            当前临时文件的文件名
	 * @param task
	 *            压缩任务
	 * @return 压缩图片的key
	 */
	protected abstract String generateResizeFileKey(String originalFileKey, String currentFilename, ImageResizeTask task);

	/**
	 * 产生一个压缩后的文件名，如果传入的压缩张数不符合期望时返回null
	 * 
	 * @param originalFileKey
	 *            {@link #generateFileKey(String)} 产生的key
	 * @param index
	 *            表示压缩过程中的第几张（从0开始）
	 * @param task
	 *            压缩任务
	 * @param ext
	 *            图片文件真实的格式
	 * @return 文件的文件名
	 * 
	 */
	protected abstract String generateFilename(String originalFileKey, int index, ImageResizeTask task, String ext);

	/**
	 * 
	 * 产生图片存放的本地地址
	 * 
	 * @param basePath
	 *            根目录
	 * @param filename
	 *            当前压缩到第几张（0开始）
	 * @param task
	 *            压缩任务
	 * @return 压缩图片的key
	 * 
	 */
	protected abstract String generateFilePath(String basePath, String filename, ImageResizeTask task);

	/**
	 * <pre>
	 * 处理图片的结果，用户可以重写该方法，比如
	 * <code>
	 * protected ImageProcessResult processResult(ResultState state, ImageResizeTask task, String[] fileKeys, ImageInfo info) {
	 * 	return new ImageProcessResult(state);
	 * }
	 * </code>
	 * </pre>
	 * 
	 * @param <E>
	 * 
	 */
	protected abstract <T extends ImageProcessResult> T processResult(ResultState state, ImageResizeTask task,
			String[] fileKeys, ImageInfo info);

	/**
	 * 删除本地目录下的文件
	 * 
	 * @param filePaths
	 *            所有压缩过程中产生的文件的路径
	 */
	protected abstract void deleteFiles(List<String> filePaths);

	/**
	 * 产生第一个文件（把数据流写成该文件）的文件名
	 * 
	 * @param fileKey
	 *            {@link #generateFileKey(String)} 产生的key
	 * @param originalFilename
	 *            源图片的自己的文件名
	 * 
	 * @return 第一个文件（把数据流写成该文件）的文件名
	 */
	protected abstract String generateFirstFilename(String fileKey, String originalFilename);

	/**
	 * 产生图片访问的键值，用于最后访问时用（设置进 {@link ImageProcessResult}）
	 * 
	 * @param task
	 *            压缩任务
	 * @return 图片访问的键值
	 */
	protected abstract String generateFileKey(ImageResizeTask task);

	/**
	 * 存储源片图片
	 * 
	 * @param firstFile
	 *            源片文件
	 * @param originalFileileKey
	 *            源片文件的键值
	 * @param fileKeys
	 *            每个大小的缩略图各自的fileKey,[0]
	 * @param task
	 *            压缩任务
	 * @return 储存处理的结果
	 */
	protected abstract ResultState storeFirstFile(File firstFile, String originalFileileKey, String[] fileKeys,
			ImageResizeTask task);

	/**
	 * 存储压缩后的图片
	 * 
	 * @param fileToStore
	 *            要储存的图片
	 * @param key
	 *            存放文件的键值
	 * @param fileKeys
	 *            每个大小的缩略图各自的fileKey,[0]为源片
	 * @param index
	 *            当前压缩的序号
	 * @param task
	 *            压缩任务
	 * @return 储存处理的结果
	 */
	protected abstract ResultState storeFile(File fileToStore, String key, String[] fileKeys, int index,
			ImageResizeTask task);

	/**
	 * 把输入流中的图片写到文件系统中
	 * 
	 * @param destFile
	 *            目标文件
	 * @param input
	 *            图片的输入流
	 * 
	 * @return 处理结果
	 */
	protected abstract ResultState writeImageToFile(File destFile, InputStream input);
}